package au.com.centrumsystems.hudson.plugin.buildpipeline;

import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import au.com.centrumsystems.hudson.plugin.util.BuildUtil;
import au.com.centrumsystems.hudson.plugin.util.HudsonResult;
import au.com.centrumsystems.hudson.plugin.util.ProjectUtil;

/**
 * @author marcinp
 * 
 *         Representation of a build results pipeline
 * 
 */
public class BuildForm extends CellForm<BuildForm> {

    public static class Builder {

        private final BuildForm.Factory factory;
        private final ProjectUtil projectUtil;
        private final BuildUtil buildUtil;

        private AbstractBuild<?, ?> build;
        private AbstractProject<?, ?> project;
        private AbstractBuild<?, ?> upstreamBuild;

        @Inject
        public Builder(BuildForm.Factory factory, ProjectUtil projectUtil, BuildUtil buildUtil) {
            this.factory = factory;
            this.projectUtil = projectUtil;
            this.buildUtil = buildUtil;
        }

        public Builder setBuild(AbstractBuild<?, ?> build) {
            this.build = build;
            return this;
        }

        public Builder setProject(AbstractProject<?, ?> project) {
            this.project = project;
            return this;
        }

        public Builder setUpstreamBuild(AbstractBuild<?, ?> upstreamBuild) {
            this.upstreamBuild = upstreamBuild;
            return this;
        }

        public BuildForm build() {
            List<BuildForm> dependencies = Lists.<BuildForm>newArrayList();
            for (AbstractProject<?, ?> downstreamProject : projectUtil.getDownstreamProjects(project)) {
                AbstractBuild<?,?> downstreamBuild = null;
                if (build != null) {
                    downstreamBuild = buildUtil.getDownstreamBuild(downstreamProject, build);
                }
                BuildForm buildForm = new BuildForm.Builder(factory, projectUtil, buildUtil)
                    .setBuild(downstreamBuild)
                    .setProject(downstreamProject)
                    .setUpstreamBuild(build)
                    .build();
                dependencies.add(buildForm);
            }
            return factory.create(build, project, dependencies, upstreamBuild);
        }
    }

    public interface Factory {
       BuildForm create(@Assisted("currentBuild") AbstractBuild<?, ?> currentBuild, AbstractProject<?, ?> project, List<BuildForm> dependencies, @Assisted("upstreamBuild") AbstractBuild<?, ?> upstreamBuild);
    }

    protected final ProjectUtil projectUtil;
    protected final BuildUtil buildUtil;

    protected final AbstractBuild<?, ?> currentBuild;
    protected final AbstractProject<?, ?> project;
    protected final AbstractBuild<?, ?> upstreamBuild;

    /**
     * @param pipelineBuild
     *            pipeline build domain used to see the form
     */
    @Inject
    public BuildForm(@Assisted("currentBuild") AbstractBuild<?, ?> currentBuild, @Assisted AbstractProject<?, ?> project, @Assisted List<BuildForm> dependencies, @Assisted("upstreamBuild") AbstractBuild<?, ?> upstreamBuild, ProjectUtil projectUtil, BuildUtil buildUtil) {
         super(dependencies);
         this.currentBuild = currentBuild;
         this.project = project;
         this.upstreamBuild = upstreamBuild;

         this.projectUtil = projectUtil;
         this.buildUtil = buildUtil;
    }

    /**
     * Returns the current build description.
     * 
     * @return - Current build description or the project name if the current build is null.
     */
    public String getName() {
        if (currentBuild != null) {
            return currentBuild.toString();
        } else {
            return "Pending build of project: " + project.getName();
        }
    }

    public String getStatus() {
        String status = buildUtil.getStatus(currentBuild);
        return (HudsonResult.PENDING.toString().equals(status)) ? getPendingStatus() : status;
    }

    private String getPendingStatus() {
        String upstreamStatus =
            buildUtil.getStatus(upstreamBuild);
        if (HudsonResult.SUCCESS.toString().equals(upstreamStatus)) {
            if (projectUtil.isManualTrigger(upstreamBuild.getProject(), project)) {
                return HudsonResult.MANUAL.toString();
            }
        }
        return HudsonResult.PENDING.toString();
    }

    /**
     * Build a URL of the currentBuild
     * 
     * @return URL of the currentBuild
     */
    public String getUrl() {
        return currentBuild != null ? currentBuild.getUrl() : "";
    }

    /**
     * Returns the current build duration.
     * 
     * @return - Current build duration or an empty String if the current build is null.
     */
    public String getDuration() {
        if (currentBuild != null) {
            return currentBuild.getDurationString();
        } else {
            return "";
        }
    }

    public boolean isManual() {
        return getStatus().equals(HudsonResult.MANUAL.toString());
    }

    public String getProjectName() {
        return currentBuild.getProject().getName();
    }

    public String getUpstreamBuildNumber() {
        if (upstreamBuild == null) {
            return "";
        }
        return String.valueOf(upstreamBuild.getNumber());
    }

    public String getUpstreamProjectName() {
        List<AbstractProject> upstreamProjects = project.getUpstreamProjects();
        if (!upstreamProjects.isEmpty()) {
            return upstreamProjects.get(0).getName();
        }
        return "";
    }

    /**
     * @return Formatted start time
     */
    public String getStartTime() {
        return DateFormat.getTimeInstance(DateFormat.FULL).format(getStartTimeAsDate());
    }

    /**
     * @return Formatted start date
     */
    public String getStartDate() {
        return DateFormat.getDateInstance(DateFormat.MEDIUM).format(getStartTimeAsDate());
    }

    private Date getStartTimeAsDate() {
        return currentBuild != null ? currentBuild.getTime() : null;

    }
}
